//
// Created by YueChuan on 2023/4/30.
//

#include "http.h"

namespace yuechuan {
    namespace http {
        HttpMethod StringToHttpMethod(const std::string &m) {
#define XX(num, name, string) \
        if(strcmp(#string, m.c_str()) == 0){ \
                return HttpMethod::name;     \
        }
            HTTP_METHOD_MAP(XX);
#undef XX
            return HttpMethod::INVALID_METHOD;
        }

        HttpMethod CharsToHttpMethod(const char *m) {
#define XX(num, name, string) \
            if(strncmp(#string, m, strlen(#string)) == 0){ \
                    return HttpMethod::name;              \
            }
            HTTP_METHOD_MAP(XX);
#undef XX
            return HttpMethod::INVALID_METHOD;
        }

        static const char *s_method_string[] = {
#define XX(num, name, string) #string,
                HTTP_METHOD_MAP(XX)
#undef XX
        };


        const char *HttpMethodToString(const HttpMethod &m) {
            uint32_t index = (uint32_t) m;
            if (index >= (sizeof(s_method_string) / sizeof(s_method_string[0]))) {
                return "<unknow>";
            }
            return s_method_string[index];
        }

        const char *HttpStatusToString(const HttpStatus &s) {
            switch (s) {
#define XX(code, name, msg) \
                case HttpStatus::name: \
                        return #msg;
                HTTP_STATUS_MAP(XX);
#undef XX
                default:
                    return "<unknow>";
            }
        }

        bool CaseInsensitiveLess::operator()(const std::string lhs, const std::string rhs) const {
            return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
        }

        HttpRequest::HttpRequest(uint8_t version, bool close) :
                m_method(HttpMethod::GET),
                m_version(version),
                m_close(close),
                m_webSocket(false),
                m_parseParamFlag(0),
                m_path("/") {

        }

        std::shared_ptr<HttpResponse> HttpRequest::createResponse() {
            HttpResponse::ptr rsp(new HttpResponse(getVersion(), isClose()));
            return rsp;
        }

        std::string HttpRequest::getHeader(const std::string &key, const std::string &def) {
            auto it = m_headers.find(key);
            return it == m_headers.end() ? def : it->second;
        }

        std::string HttpRequest::getParam(const std::string &key, const std::string &def) {
            initQueryParam();
            initBodyParam();
            auto it = m_params.find(key);
            return it == m_params.end() ? def : it->second;
        }

        std::string HttpRequest::getCookie(const std::string &key, const std::string &def) {
            initCookies();
            auto it = m_cookies.find(key);
            return it == m_cookies.end() ? def : it->second;
        }

        void HttpRequest::setHeader(const std::string &key, const std::string &value) {
            m_headers[key] = value;
        }

        void HttpRequest::setParam(const std::string &key, const std::string &value) {
            m_params[key] = value;
        }

        void HttpRequest::setCookie(const std::string &key, const std::string &value) {
            m_cookies[key] = value;
        }

        void HttpRequest::delHeader(const std::string &key) {
            m_headers.erase(key);
        }

        void HttpRequest::delParam(const std::string &key) {
            m_params.erase(key);
        }

        void HttpRequest::delCookie(const std::string &key) {
            m_cookies.erase(key);
        }

        bool HttpRequest::hasHeader(const std::string &key, std::string *val) {
            auto it = m_headers.find(key);
            if (it == m_headers.end()) {
                return false;
            }
            if (val) {
                *val = it->second;
            }
            return true;
        }

        bool HttpRequest::hasParam(const std::string &key, std::string *val) {
            auto it = m_params.find(key);
            if (it == m_params.end()) {
                return false;
            }
            if (val) {
                *val = it->second;
            }
            return true;

        }

        bool HttpRequest::hasCookie(const std::string &key, std::string *val) {
            auto it = m_cookies.find(key);
            if (it == m_cookies.end()) {
                return false;
            }
            if (val) {
                *val = it->second;
            }
            return true;
        }

        std::string HttpRequest::toString() const {
            std::stringstream ss;
            dump(ss);
            return ss.str();
        }

        std::ostream &HttpRequest::dump(std::ostream &os) const {
            os << HttpMethodToString(m_method) << " "
               << m_path << (m_query.empty() ? "" : "?") << m_query
               << (m_fragment.empty() ? "" : "#") << m_fragment
               << " HTTP/"
               << ((uint32_t) (m_version >> 4)) << "."
               << ((uint32_t) (m_version & 0X0F)) << "\r\n";
            if (!m_webSocket) {
                os << "connection: " << (m_close ? "close" : "keep-alive") << "\r\n";
            }
            for (auto &it: m_headers) {
                if (!m_webSocket && strcasecmp(it.first.c_str(), "connection") == 0) {
                    continue;
                }
                os << it.first << ": " << it.second << "\r\n";
            }
            if (!m_body.empty()) {
                if(m_method == HttpMethod::POST && strcasecmp(getHeader("content-type").c_str(), "image/jpeg")){
                    // 图片上传不需要显示请求体
                    os << "content-length: " << m_body.size() << "\r\n\r\n";

                }else{
                    os << "content-length: " << m_body.size() << "\r\n\r\n" << m_body;
                }
            } else {
                os << "\r\n";
            }
            return os;
        }

        void HttpRequest::init() {
            std::string conn = getHeader("connection");
            if (!conn.empty()) {
                if (strcasecmp(conn.c_str(), "keep-alive") == 0) {
                    m_close = false;
                } else {
                    m_close = true;
                }
            }
            initParam();
        }

        void HttpRequest::initParam() {
            initQueryParam();
            initBodyParam();
            initCookies();
        }

        void HttpRequest::initQueryParam() {
            if (m_parseParamFlag & 0X1) {
                return;
            }
#define PARSE_PARAM(str, m, flag, trim) \
            size_t pos = 0; \
            do { \
                size_t last = pos; \
                pos = str.find('=', pos); \
                if(pos == std::string::npos) { \
                    break; \
                } \
                size_t key = pos; \
                pos = str.find(flag, pos); \
                m.insert(std::make_pair(trim(str.substr(last, key - last)), \
                            yuechuan::StringUtil::UrlDecode(str.substr(key + 1, pos - key - 1)))); \
                if(pos == std::string::npos) { \
                    break; \
                } \
                ++pos; \
            } while(true);

            PARSE_PARAM(m_query, m_params, '&',);
            m_parseParamFlag |= 0x1;
        }

        void HttpRequest::initBodyParam() {
            if (m_parseParamFlag & 0x2) {
                return;
            }
            std::string content_type = getHeader("content-type");
    
            if(strcasestr(content_type.c_str(), "image/jpeg") != nullptr){
                ///TODO 对于图片类型上传处理 save pic to local

            }
            if (strcasestr(content_type.c_str(), "application/x-www-form-urlencoded") == nullptr) {
                m_parseParamFlag |= 0x2;
                return;
            }

            PARSE_PARAM(m_body, m_params, '&',);
            ///TODO 这里完成了请求体的解析，是否要对文件上传进行处理？
            ///ANS  这里的m_body是字符串类型，对于文件类型要进行特殊处理
            m_parseParamFlag |= 0x2;
        }

        void HttpRequest::initCookies() {
            if (m_parseParamFlag & 0x2) {
                return;
            }
            std::string cookie = getHeader("cookie");
            if (cookie.empty()) {
                m_parseParamFlag |= 0x4;
                return;
            }
            PARSE_PARAM(cookie, m_cookies, ';', yuechuan::StringUtil::Trim);
            m_parseParamFlag |= 0x4;
        }

/// ------------------------------------------------------------------------------------------------------------------------------------

        HttpResponse::HttpResponse(uint8_t version, bool close) :
                m_status(HttpStatus::OK),
                m_version(version),
                m_close(close),
                m_webSocket(false) {

        }

        std::string HttpResponse::getHeader(const std::string &key, const std::string &def) const {
            auto it = m_headers.find(key);
            return it == m_headers.end() ? def : it->second;
        }

        void HttpResponse::setHeader(const std::string &key, const std::string &value) {
            m_headers[key] = value;
        }

        void HttpResponse::delHeader(const std::string &key) {
            m_headers.erase(key);
        }

        void HttpResponse::setRedirect(const std::string &uri) {
            m_status = HttpStatus::FOUND;
            setHeader("Location", uri);
        }

        void HttpResponse::setCookie(const std::string &key, const std::string &val, time_t expired,
                                     const std::string &path, const std::string &domain, bool secure) {
            std::stringstream ss;
            ss << key << "=" << val;
            if (expired > 0) {
                ss << ";expires=" << Time2Str(expired, "%a, %d %b %y %H:%M:%S") << " GMT";
            }
            if (!domain.empty()) {
                ss << ";domain=" << domain;
            }
            if (!path.empty()) {
                ss << ";path=" << path;
            }
            if (secure) {
                ss << ";secure";
            }
            m_cookies.emplace_back(ss.str());
        }

        std::string HttpResponse::toString() const {
            std::stringstream ss;
            dump(ss);
            return ss.str();
        }

        std::ostream &HttpResponse::dump(std::ostream &os) const {
            os << "HTTP/" << ((uint32_t) (m_version >> 4)) << "."
               << ((uint32_t) (m_version & 0x0F)) << " "
               << ((uint32_t) m_status) << " "
               << (m_reason.empty() ? HttpStatusToString(m_status) : m_reason)
               << "\r\n";
            for (auto &i : m_headers) {
                if (!m_webSocket && strcasecmp(i.first.c_str(), "connection") == 0) {
                    continue;
                }
                os << i.first << ": " << i.second << "\r\n";
            }
            for (auto &i: m_cookies) {
                os << " set-Cookie: " << i << "\r\n";
            }
            if (!m_webSocket) {
                os << "connection: " << (m_close ? "close" : "keep-alive") << "\r\n";
            }
            if (!m_body.empty()) {
                os << "content-length: " << m_body.size() << "\r\n\r\n" << m_body;
            } else {
                os << "\r\n";
            }
            return os;
        }

        std::ostream &operator<<(std::ostream &os, const HttpRequest &req) {
            return req.dump(os);
        }

        std::ostream &operator<<(std::ostream &os, const HttpResponse &rsp) {
            return rsp.dump(os);
        }
    }
}
